#ifndef OPENMPKERNEL_H
#define OPENMPKERNEL_H

#include <omp.h>
#include <algorithm>

#include "../../QueryDataManipulation/src/QueryDataManipulation.hpp"
#include "../../DMMath/src/DMMath.hpp"

template<typename DataIterator, typename SelectType = DataIterator> class OpenMpKernel : public QDM::IKernel<DataIterator, SelectType>
{
	typedef long int Index;

public:
	OpenMpKernel() : _countThreads(1) {}

	virtual void where(ResultVector& resultVector, WhereFunction isRequired) const
	{
		std::set<Index> searchSegments = KDM::math::getThreadSegments(resultVector, _countThreads);

		Index countThreads = (Index)searchSegments.size() - 1;;

		std::vector<Index> borders;
		std::vector<Index> filterBorders(countThreads);

		for (std::set<Index>::iterator item = searchSegments.begin(); item != searchSegments.end(); item++)
		{
			borders.push_back(*item);
		}

		omp_set_num_threads(countThreads);
		#pragma omp parallel for
		for (Index i = 0; i < countThreads; i++)
		{
      KDM::math::search(resultVector, borders[i], borders[i + 1], isRequired, filterBorders[i]);
		}

		KDM::math::eraseExcessElements(resultVector, filterBorders, searchSegments);
	}

	virtual void orderBy(ResultVector& resultVector, OrderByFunction isNeedChange) const
	{
		Index resultVectorSize = (Index)resultVector.size();
		Index left = 0, right = resultVectorSize;

		if (left < right)
		{
			Index segmentsCount = KDM::math::getOrderBySegmentsCount(_countThreads, resultVectorSize);

			std::set<Index> sortBordersSegments;

			sortBordersSegments.insert(left);
			sortBordersSegments.insert(right);

			std::set<Index>::iterator sortBordersSegmentsIterator;

			Index countThreads;

			std::vector<Index> borders;

			for (Index i = 1; i < segmentsCount; i *= 2)
			{
				sortBordersSegmentsIterator = sortBordersSegments.begin();

				countThreads = (Index)sortBordersSegments.size() - 1;

				std::vector<Index> terminators(countThreads);
				
				borders.resize(countThreads + 1);

				for (Index j = 0; j < countThreads + 1; j++)
				{
					borders[j] = *sortBordersSegmentsIterator++;
				}

				std::vector<DataIterator> medians(countThreads);

				omp_set_num_threads(countThreads);
				#pragma omp parallel for
				for (Index j = 0; j < countThreads; j++)
				{
					KDM::math::hoarSortStep(resultVector, borders[j], borders[j + 1], isNeedChange, terminators[j], medians[j]);
				}

				for (Index j = 0; j < countThreads; j++)
				{
					sortBordersSegments.insert(terminators[j]);
				}
			}

			sortBordersSegmentsIterator = sortBordersSegments.begin();

			countThreads = (Index)sortBordersSegments.size() - 1;

			borders.resize(countThreads + 1);

			for (Index j = 0; j < countThreads + 1; j++)
			{
				borders[j] = *sortBordersSegmentsIterator++;
			}
			
			omp_set_num_threads(countThreads);
			#pragma omp parallel for
			for (Index i = 0; i < countThreads; i++)
			{
				KDM::math::sortSegment(resultVector, borders[i], borders[i + 1], isNeedChange);
			}
		}
	}

	virtual void select(const ResultVector& resultVector, SelectVector& resultSelect, SelectFunction selectFunction) const
	{
    Index resultVectorSize = (Index)resultVector.size();
    resultSelect.resize(resultVectorSize);

    const std::set<Index> selectSegments = KDM::math::getThreadSegments(resultVector, _countThreads);

    Index separaterIndex = 0;
    Index countThreads = (Index)selectSegments.size() - 1;

    std::set<Index>::const_iterator selectSegmentsIterator = selectSegments.cbegin();

		std::vector<Index> borders;

		borders.resize(countThreads + 1);

		for (Index i = 0; i < countThreads + 1; i++)
		{
			borders[i] = *selectSegmentsIterator++;
		}

		omp_set_num_threads(countThreads);
		#pragma omp parallel for
		for (Index i = 0; i < countThreads; i++)
		{
			KDM::math::select(resultVector, resultSelect, borders[i], borders[i + 1], selectFunction);
		}
	}

	virtual void execute(ResultVector& resultVector, ExecuteFunction executeFunction) const
	{
		omp_set_num_threads(_countThreads);
		#pragma omp parallel for
		for (Index i = 0; i < _countThreads; i++)
		{
			executeFunction(resultVector, i);
		}
	}

	void setCountThreads(Index countThreads){_countThreads = countThreads;}
	Index getCountThreads() const {return _countThreads;}

private:
	Index _countThreads;
};

#endif